{- «•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»

    Copyright © 2011 - 2021, Ingo Wechsung
    All rights reserved.

    Redistribution and use in source and binary forms, with or
    without modification, are permitted provided that the following
    conditions are met:

        Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.

        Redistributions in binary form must reproduce the above
        copyright notice, this list of conditions and the following
        disclaimer in the documentation and/or other materials provided
        with the distribution. Neither the name of the copyright holder
        nor the names of its contributors may be used to endorse or
        promote products derived from this software without specific
        prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE
    COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
    OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
    USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
    AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
    IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
    THE POSSIBILITY OF SUCH DAMAGE.

    «•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•»«•» -}

{--
    Provides position ranges made from two 'Token's.
    
    Also, interface 'Positioned' to get the 'Position' of certain values.
 -}


package frege.compiler.types.Positions where

import Compiler.enums.TokenID
import Compiler.types.Tokens

--- things that know where they were introduced and what they are
class Positioned a where
    --- Get the position of an item.
    --- This is used for eror messages and the like
    getpos  :: a -> Position
    --- This should span the full extent of the item
    getrange :: a -> Position
    is       :: a -> String       -- pattern, expr, ....

    --- Falls back to 'getpos', if not defined.
    getrange = getpos


--- make a 'Position' from a 'Token'. 
--- 'Position.first' and 'Position.last' of the result will be the passed token.
positionOf token = Pos token token


instance Positioned Position where
    getpos pos = pos
    is _       = "position"


instance Eq Position where
    pos1 == pos2 = pos1.start == pos2.start && pos1.end == pos2.end
    hashCode pos = 31*hashCode pos.first + hashCode pos.last


instance Ord Position where
    pos1 <=> pos2 = pos1.start <=> pos2.start


instance Show Position where
    show pos = show pos.line


data Position = Pos { !first, !last :: Token } where
    --- overwrite 'first's token id and value and set last = first
    --- used to construct custom tokens for generated code
    change :: Position -> TokenID -> String -> Position
    change p t s = Pos f f where f = p.first.{tokid = t, value = s}
    --- pseudo position, does not influence merge operations
    null = Pos n n where
        n = Token LEXERROR "?¿" 1 0 0 []
    --- Make a new position that subsumes both
    merge :: Position -> Position -> Position
    merge pos1 pos2
        | pos1 == null = pos2
        | pos2 == null = pos1
        | pos2.start >= pos1.start && pos2.end <= pos1.end = pos1       -- already subsumed
        | pos1.start >= pos2.start && pos1.end <= pos2.end = pos2       -- dito
        | pos1.start < pos2.start = Pos pos1.first pos2.last
        | otherwise               = Pos pos2.first pos1.last
    --- Merge a list of positions
    merges :: [Position] -> Position
    merges = fold merge null
    --- get the line number where it starts
    line  Pos{first} = first.line
    --- get start offset of item
    start Pos{first} = first.offset
    --- get first offset after item
    end   Pos{last}  = last.offset + last.length
